package cn.rulian.key;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

import cn.rulian.base.common.util.MD5Util;
import cn.rulian.base.common.util.xx;



/** 
 * 系统防盗版工具类（全版）
 * 
 * <br/>  1、该 类放在测试目录下，既保证项目文件的完整性（防止日久忘记），同时还保证文件打包时，不被打入项目发布包中。
 * <br/>  2、Dm类是全加解密类，供开发人员使用。可简单修改main()方法后，用于生成key。
 * <br/>  3、系统中的Dm2类，是在Dm类基础上删减而得，是客户端版，主要用于验证系统key的合法性。
 */
public class Dm
{
	private static String info; //本机版本信息（显示当前注册序列号的情况）
	
	public static void main(String[] args) {

		/* 计算类型：
		 * （1）test：计算试用版key，时长不能超过一年
		 * （2）local：计算当前机器key
		 * （3）prod：计机正式服务器key，需要提供服务器的机器码
		 */
		String type = "test";
		//String type = "local";
		//String type = "prod";
		
		//正式服务器机器码(32位)，当 type 值是 prod 时必填
		String machineNum = "2BBEABB8D9080B14F0E38782EDD8F4C9";
		
		//到期日期
		String endDateStr = "2020-10-01"; 
		
		//------------------------------------
		
		Dm dm = new Dm();
		
		if("test".equals(type)){
			machineNum = MD5Util.MD5("test"); //试用版本机器码
			//System.out.println("试用版模拟机器码："+machineNum);
			System.out.println("试用版Key：");
		}
		else if("local".equals(type)){
			machineNum = dm.getMachineNum(); //本机机器码
			//System.out.println("获取本机机器码："+machineNum);
			System.out.println("本机Key：");
		}
		else if("prod".equals(type)){

			System.out.println("服务器Key：");
		}
		
		Date endDate = xx.toDate(endDateStr); //到期日（该天非有效）
		String key = dm.getKey(machineNum, endDate);
		System.out.println(key);

		//System.out.println("验证key结果："+dm.pass(key));
		
	}

	/**
	 * 获取key
	 * @param machineNum 机器码
	 * @param endDate 最后日期
	 * @return 有效的key <br/>
	 * 
	 * 加密思想说明： <br/>
	 * （1）机器码：非真正意义上的机器码，而是通过几个系统环境参数组合，再经过MD5加密，得到的一个32位串。因为两个不同的机器，环境参数极有可能不完全相同，只要有一点不同，得到的机器码就不同。 <br/>
	 * （2）试用版机器码：通过MD5加密“test”而得到。 <br/>
	 * （3）最后日期：即到期日期，当天即失效。 <br/>
	 * （4）标志：区分【试用版】和【正式版】，试用版 00，正式版 11。 <br/>
	 * （5）加密步骤：32机器码中间分开，加上8位最后日期，8位最后日期再中间分开，加上2位标志。最后将整个串（42位）进行可逆加密，得到key。 <br/>
	 * （6）解决说明：先解密key，再根据不同位置，拼出标志、机器码、最后日期，然后依次判断其有效性。 <br/>
	 * 
	 * 使用规约：<br/>
	 * （1）试用版key，只用于前期试用，绝对不能计算长期（如：大于1年）试用版。因为那个码对所有机器都能长期使用哟。<br/>
	 * （2）正式版key，在用户结清尾款后，计算了一超长期（如：3099-01-01）的key给用户。<br/>
	 */
	public String getKey(String machineNum, Date endDate)
	{
		if(xx.isEmpty(endDate) || xx.isEmpty(machineNum) || machineNum.length() != 32)
		{
			return null;
		}
		SimpleDateFormat df = new SimpleDateFormat("MMyyyydd");
		String endStr = df.format(endDate); //到期日
		String sign = "11"; //标志
		if(machineNum.equals(MD5Util.MD5("test")))
		{
			sign = "00";
		}
		
		if("00".equals(sign) && xx.getDate(new Date(), 365).before(endDate))
		{//不允许计算大于1年的试用key
			return "不能计算长期的试版key";
		}
		
		String key = machineNum.substring(0,16)+endStr.substring(0,4)+sign+endStr.substring(4)+machineNum.substring(16);
		return this.jia(key);
	}
	
	/**
	 * 验证指定Key是否有效
	 */
	public boolean pass(String key)
	{
		if(xx.isEmpty(key))
		{
			info = "空序列号";
			return false;
		}
		if(key.length() != 42)
		{
			info = "非法序列号";
			return false;
		}
		
		key = this.jie(key);
		String sign = key.substring(20, 22); //标志：最中间2位
		String endStr = key.substring(16, 20) + key.substring(22, 26); //到期日：次两边各4位。
		String machineNum = key.substring(0, 16) + key.substring(26); //机器码：外两边各16位
		
		//验证标志位
		if(!"11".equals(sign) && !"00".equals(sign))
		{
			info = "非法序列号";
			return false;
		}
		
		//验证机器码
		if("00".equals(sign) && (!machineNum.equals(MD5Util.MD5("test"))))
		{
			info = "非法序列号";
			return false;
		}
		if("11".equals(sign) && (!machineNum.equals(this.getMachineNum())))
		{
			info = "无效正式序列号（非本机环境）";
			return false;
		}
		
		//显示信息
		String type ="正式序列号";
		if("00".equals(sign))
		{
			type = "测试序列号";
		}
		String endDate = null;
		
		
		//验证到期日
		try
		{
			SimpleDateFormat df = new SimpleDateFormat("MMyyyydd");
			Date end = df.parse(endStr);
			endDate = xx.getDateStr(end);
			if (end.before(new Date()))
			{
				info = type + "，有效期至：" + endDate +"，已过期！！！";
				return false;
			}
		} catch (ParseException e)
		{
			info = "非法序列号";
			return false;
		}

		info = type + "，有效期至：" + endDate ;
		return true;
	}

	/**
	 * 解密字符串
	 */
	private String jie(String s)
	{
		byte[] abyte0;
		char[] ac;
		int i;
		int k;
		int j;
		abyte0 = s.getBytes();
		ac = new char[s.length()];
		i = 0;
		k = abyte0.length;
		while (i < k)
		{
			j = abyte0[i];
			if ((j >= 48) && (j <= 57))
			{
				j = (((j - 48) + 6) % 10) + 48;
			} 
			else if ((j >= 65) && (j <= 90))
			{
				j = (((j - 65) + 16) % 26) + 65;
			} 
			else if ((j >= 97) && (j <= 122))
			{
				j = (((j - 97) + 16) % 26) + 97;
			}
			ac[i] = (char) j;
			i++;
		}
		return String.valueOf(ac);
	}

	/**
	 * 加密字符串
	 */
	private String jia(String s)
	{
		byte[] abyte0;
		char[] ac;
		int i;
		int k;
		int j;
		abyte0 = s.getBytes();
		ac = new char[s.length()];
		i = 0;
		k = abyte0.length;
		while (i < k)
		{
			j = abyte0[i];
			if ((j >= 48) && (j <= 57))
			{
				j = (((j - 48) + 4) % 10) + 48;
			} else if ((j >= 65) && (j <= 90))
			{
				j = (((j - 65) + 10) % 26) + 65;
			} else if ((j >= 97) && (j <= 122))
			{
				j = (((j - 97) + 10) % 26) + 97;
			}
			ac[i] = (char) j;
			i++;
		}
		return String.valueOf(ac);
	}
	
	/**
	 * 功能描述：得到机器码
	 */
	public String getMachineNum()
	{
		Properties pro = System.getProperties();
		StringBuilder str = new StringBuilder();
		str.append(pro.getProperty("user.dir"));
		str.append(pro.getProperty("os.name"));
		str.append(pro.getProperty("os.version"));
		str.append(pro.getProperty("java.home"));
		str.append(pro.getProperty("java.version"));
		
		return MD5Util.MD5(str.toString());
	}

	public static String getInfo()
	{
		return info;
	}


}
